use super::*;

use crate::{
    prefix_type::{PrefixRef, PrefixRefTrait},
    sabi_types::{Constructor, RRef},
};

/// Used to check the layout of modules returned by module-loading functions
/// exported by dynamic libraries.
///
/// Module-loading functions are declared with the [`export_root_module`] attribute.
///
/// [`export_root_module`]: ../attr.export_root_module.html
#[repr(C)]
#[derive(StableAbi)]
pub struct LibHeader {
    header: AbiHeader,
    root_mod_consts: ErasedRootModuleConsts,
    init_globals_with: InitGlobalsWith,
    module: LateStaticRef<PrefixRef<ErasedPrefix>>,
    constructor: Constructor<RootModuleResult>,
}

impl LibHeader {
    /// Constructs a LibHeader from the root module loader.
    ///
    /// # Safety
    ///
    /// The `PrefixRef<ErasedPrefix>` returned by the function that
    /// `constructor ` wraps must be have been transmuted from a `PrefixRef<M>`.
    pub const unsafe fn from_constructor<M>(
        constructor: Constructor<RootModuleResult>,
        root_mod_consts: RootModuleConsts<M>,
    ) -> Self {
        Self {
            header: AbiHeader::VALUE,
            root_mod_consts: root_mod_consts.erased(),
            init_globals_with: INIT_GLOBALS_WITH,
            module: LateStaticRef::new(),
            constructor,
        }
    }

    /// Constructs a LibHeader from the module.
    pub fn from_module<T>(value: T) -> Self
    where
        T: RootModule,
    {
        Self {
            header: AbiHeader::VALUE,
            root_mod_consts: T::CONSTANTS.erased(),
            init_globals_with: INIT_GLOBALS_WITH,
            module: {
                let erased = unsafe { value.to_prefix_ref().cast::<ErasedPrefix>() };
                LateStaticRef::from_prefixref(PrefixRefTrait::PREFIX_FIELDS, erased)
            },
            constructor: GetAbortingConstructor::ABORTING_CONSTRUCTOR,
        }
    }

    /// All the important constants of a `RootModule` for some erased type.
    pub fn root_mod_consts(&self) -> &ErasedRootModuleConsts {
        &self.root_mod_consts
    }

    /// The version string of the library the module is being loaded from.
    pub fn version_strings(&self) -> VersionStrings {
        self.root_mod_consts.version_strings()
    }

    /// Gets the layout of the root module.
    ///
    /// This returns a None if the root module layout is not included
    /// because the `#[unsafe_no_layout_constant]`
    /// helper attribute was used on the function exporting the root module.
    pub fn layout(&self) -> Option<&'static TypeLayout> {
        self.root_mod_consts.layout().into_option()
    }

    pub(super) fn initialize_library_globals(&self, globals: &'static Globals) {
        (self.init_globals_with.0)(globals);
    }

    fn check_version<M>(&self) -> Result<(), LibraryError>
    where
        M: RootModule,
    {
        let expected_version = M::VERSION_STRINGS.piped(VersionNumber::new)?;

        let actual_version = self.version_strings().piped(VersionNumber::new)?;

        if expected_version.major != actual_version.major
            || (expected_version.major == 0) && expected_version.minor > actual_version.minor
        {
            return Err(LibraryError::IncompatibleVersionNumber {
                library_name: M::NAME,
                expected_version,
                actual_version,
            });
        }
        Ok(())
    }

    /// Checks that the library is compatible, returning the root module on success.
    ///
    /// It checks that these are compatible:
    ///
    /// - The version number of the library
    ///
    /// - The layout of the root module.
    ///
    /// # Warning
    ///
    /// If this function is called within a dynamic library,
    /// it must be called at or after the function that exports its root module is called.
    ///
    /// **DO NOT** call this in the static initializer of a dynamic library,
    /// since this library relies on setting up its global state before
    /// calling the root module loader.
    ///
    /// # Errors
    ///
    /// This returns these errors:
    ///
    /// - `LibraryError::ParseVersionError`:
    /// If the version strings in the library can't be parsed as version numbers,
    /// this can only happen if the version strings are manually constructed.
    ///
    /// - `LibraryError::IncompatibleVersionNumber`:
    /// If the version number of the library is incompatible.
    ///
    /// - `LibraryError::AbiInstability`:
    /// If the layout of the root module is not the expected one.
    ///
    /// - `LibraryError::RootModule` :
    /// If the root module initializer returned an error or panicked.
    ///
    ///
    pub fn init_root_module<M>(&self) -> Result<M, LibraryError>
    where
        M: RootModule,
    {
        self.check_version::<M>()?;
        self.check_layout::<M>()
    }

    /// Checks that the version number of the library is compatible,
    /// returning the root module on success.
    ///
    /// This function transmutes the root module type,
    /// without checking that the layout is compatible first.
    ///
    /// # Warning
    ///
    /// If this function is called within a dynamic library,
    /// it must be called at or after the function that exports its root module is called.
    ///
    /// **DO NOT** call this in the static initializer of a dynamic library,
    /// since this library relies on setting up its global state before
    /// calling the root module loader.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `M` has the expected layout.
    ///
    /// # Errors
    ///
    /// This returns these errors:
    ///
    /// - `LibraryError::ParseVersionError`:
    /// If the version strings in the library can't be parsed as version numbers,
    /// this can only happen if the version strings are manually constructed.
    ///
    /// - `LibraryError::IncompatibleVersionNumber`:
    /// If the version number of the library is incompatible.
    ///
    /// - `LibraryError::RootModule` :
    /// If the root module initializer returned an error or panicked.
    ///
    ///
    pub unsafe fn init_root_module_with_unchecked_layout<M>(&self) -> Result<M, LibraryError>
    where
        M: RootModule,
    {
        self.check_version::<M>()?;
        self.unchecked_layout()
            .map_err(RootModuleError::into_library_error::<M>)
    }

    /// Checks that the layout of the `M` root module from the dynamic library is
    /// compatible with the expected layout.
    ///     
    /// # Errors
    ///
    /// This returns these errors:
    ///
    /// - `LibraryError::AbiInstability`:
    /// If the layout of the root module is not the expected one.
    ///
    /// - `LibraryError::RootModule` :
    /// If the root module initializer returned an error or panicked.
    ///
    pub fn ensure_layout<M>(&self) -> Result<(), LibraryError>
    where
        M: RootModule,
    {
        if let IsLayoutChecked::Yes(root_mod_layout) = self.root_mod_consts.layout() {
            // Using this instead of
            // crate::abi_stability::abi_checking::check_layout_compatibility
            // so that if this is called in a dynamic-library that loads
            // another dynamic-library,
            // it uses the layout checker of the executable,
            // ensuring a globally unique view of the layout of types.
            //
            // This might also reduce the code in the library,
            // because it doesn't have to compile the layout checker for every library.
            (globals::initialized_globals().layout_checking)(<M>::LAYOUT, root_mod_layout)
                .into_result()
                .map_err(|e| {
                    // Fixes the bug where printing the error causes a segfault because it
                    // contains static references and function pointers into the unloaded library.
                    //
                    // This isn't strictly required anymore because abi_stable doesn't
                    // unload libraries right now.
                    let formatted = e.to_formatted_error();
                    LibraryError::AbiInstability(formatted)
                })?;
        }

        atomic::compiler_fence(atomic::Ordering::SeqCst);

        Ok(())
    }

    /// Gets the root module,first
    /// checking that the layout of the `M` from the dynamic library is
    /// compatible with the expected layout.
    ///     
    /// # Errors
    ///
    /// This returns these errors:
    ///
    /// - `LibraryError::AbiInstability`:
    /// If the layout of the root module is not the expected one.
    ///
    /// - `LibraryError::RootModule` :
    /// If the root module initializer returned an error or panicked.
    ///
    pub fn check_layout<M>(&self) -> Result<M, LibraryError>
    where
        M: RootModule,
    {
        self.ensure_layout::<M>()?;

        unsafe {
            self.unchecked_layout()
                .map_err(RootModuleError::into_library_error::<M>)
        }
    }

    /// Gets the root module without checking that the layout of `M` is the expected one.
    /// This is effectively a transmute.
    ///
    /// This is useful if a user keeps a cache of which dynamic libraries
    /// have been checked for layout compatibility.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `M` has the expected layout.
    ///
    /// # Errors
    ///
    /// This function can return a `RootModuleError`
    /// because the root module failed to initialize.
    ///
    pub unsafe fn unchecked_layout<M>(&self) -> Result<M, RootModuleError>
    where
        M: PrefixRefTrait,
    {
        self.module
            .try_init(|| (self.constructor.0)().into_result())
            .map_err(|mut err| {
                // Making sure that the error doesn't contain references into
                // the unloaded library.
                //
                // This isn't strictly required anymore because abi_stable doesn't
                // unload libraries right now.
                err.reallocate();
                err
            })?
            .cast::<M::PrefixFields>()
            .piped(M::from_prefix_ref)
            .piped(Ok)
    }
}

//////////////////////////////////////////////////////////////////////

struct GetAbortingConstructor<T>(T);

impl<T> GetAbortingConstructor<T> {
    const ABORTING_CONSTRUCTOR: Constructor<T> = Constructor(Self::aborting_constructor);

    extern "C" fn aborting_constructor() -> T {
        extern_fn_panic_handling! {
            panic!(
                "BUG:\n\
                 This function \
                 (abi_stable::library::lib_header::GetAbortingConstructor::aborting_constructor) \
                 must only be used \
                 as a dummy functions when initializing `LibHeader` \
                 within `LibHeader::from_module`."
            );
        }
    }
}

//////////////////////////////////////////////////////////////////////

#[repr(C)]
#[derive(StableAbi, Copy, Clone)]
struct InitGlobalsWith(pub extern "C" fn(&'static Globals));

const INIT_GLOBALS_WITH: InitGlobalsWith = InitGlobalsWith(crate::globals::initialize_globals_with);

//////////////////////////////////////////////////////////////////////

/// A handle to the [`AbiHeader`] of a library.
///
/// This can be dereferenced into the `AbiHeader`,
/// and [fallibly upgraded](#method.upgrade) into a `&'static LibHeader`.
///
/// [`AbiHeader`]: ./struct.AbiHeader.html
#[repr(transparent)]
#[derive(Debug, StableAbi, Copy, Clone)]
pub struct AbiHeaderRef(pub(super) RRef<'static, AbiHeader>);

impl std::ops::Deref for AbiHeaderRef {
    type Target = AbiHeader;

    fn deref(&self) -> &AbiHeader {
        self.0.get()
    }
}

/// Represents the abi_stable version used by a compiled dynamic library,
/// which if incompatible would produce a [`LibraryError::InvalidAbiHeader`]
///
/// [`LibraryError::InvalidAbiHeader`]: ./enum.LibraryError.html#variant.InvalidAbiHeader
#[repr(C)]
#[derive(Debug, StableAbi, Copy, Clone)]
// This type will never have new fields clippy, that's the point <_<
#[allow(clippy::manual_non_exhaustive)]
pub struct AbiHeader {
    /// A magic string used to check that this is actually abi_stable.
    pub magic_string: [u8; 32],
    /// The major abi version of abi_stable
    pub abi_major: u32,
    /// The minor abi version of abi_stable
    pub abi_minor: u32,
    _priv: (),
}

impl AbiHeader {
    /// The value of the AbiHeader stored in dynamic libraries that use this
    /// version of abi_stable
    pub const VALUE: AbiHeader = {
        mod value {
            use super::*;
            abi_stable_derive::construct_abi_header! {}
        }
        value::ABI_HEADER
    };
}

impl AbiHeader {
    /// Checks whether this AbiHeader is compatible with `other`.
    pub fn is_compatible(&self, other: &Self) -> bool {
        self.magic_string == other.magic_string
            && self.abi_major == other.abi_major
            && (self.abi_major != 0 || self.abi_minor == other.abi_minor)
    }

    /// Checks whether the abi_stable version of this AbiHeader is
    /// compatible with the one from where this function is called.
    pub fn is_valid(&self) -> bool {
        self.is_compatible(&AbiHeader::VALUE)
    }
}

impl AbiHeaderRef {
    /// Gets the LibHeader of a library.
    ///
    /// # Errors
    ///
    /// This returns these errors:
    ///
    /// - `LibraryError::InvalidAbiHeader`:
    /// If the abi_stable used by the library is not compatible.
    ///
    /// - `LibraryError::InvalidCAbi`:
    /// If the C abi used by the library is not compatible.
    pub fn upgrade(self) -> Result<&'static LibHeader, LibraryError> {
        if !self.is_valid() {
            return Err(LibraryError::InvalidAbiHeader(*self));
        }

        let lib_header: &'static LibHeader = unsafe { self.0.transmute_into_ref() };

        let c_abi_testing_fns = lib_header.root_mod_consts().c_abi_testing_fns();
        crate::library::c_abi_testing::run_tests(c_abi_testing_fns)?;

        let globals = globals::initialized_globals();

        lib_header.initialize_library_globals(globals);

        Ok(lib_header)
    }
}
